#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Licensed to Cloudera, Inc. under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  Cloudera, Inc. licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import datetime
import glob
import logging
import os
import socket
import stat
import sys
from collections import OrderedDict

from django.db import connection
from django.utils.translation import gettext_lazy as _

from desktop import appmanager
from desktop.lib.conf import (
  coerce_bool,
  coerce_csv,
  coerce_json_dict,
  coerce_password_from_script,
  coerce_str_lowercase,
  coerce_string,
  Config,
  ConfigSection,
  list_of_compiled_res,
  UnspecifiedConfigSection,
  validate_path,
)
from desktop.lib.i18n import force_unicode
from desktop.lib.paths import get_desktop_root, get_run_root
from desktop.redaction.engine import parse_redaction_policy_from_file
from metadata.metadata_sites import get_navigator_audit_log_dir, get_navigator_audit_max_file_size

LOG = logging.getLogger()


def is_oozie_enabled():
  """Oozie needs to be available as it is the backend."""
  return len([app for app in appmanager.DESKTOP_MODULES if app.name == 'oozie']) > 0


def coerce_database(database):
  if database == 'mysql':
    return 'django.db.backends.mysql'
  elif database == 'postgres' or database == 'postgresql_psycopg2':
    return 'django.db.backends.postgresql_psycopg2'
  elif database == 'oracle':
    return 'django.db.backends.oracle'
  elif database in ('sqlite', 'sqlite3'):
    return 'django.db.backends.sqlite3'
  else:
    return str(database)


def coerce_port(port):
  try:
    port = int(port)
    if port == 0:
      port = ''
  except ValueError:
    port = ''
  return port


def coerce_file(path):
  if path and not os.path.isfile(path):
    raise Exception('File %s does not exist.' % path)
  return path


def coerce_timedelta(value):
  return datetime.timedelta(seconds=int(value))


def get_dn(fqdn=None):
  """This function returns fqdn(if possible)"""
  val = []
  LOG = logging.getLogger()
  try:
    if fqdn is None:
      fqdn = socket.getfqdn()

    if '.' in fqdn:
      tups = fqdn.split('.')
      if len(tups) > 2:
        val.append(".%s" % ('.'.join(tups[-2:])))
      else:
        LOG.warning("allowed_hosts value to '*'. It is a security risk")
        val.append('*')
    else:
      LOG.warning("allowed_hosts value to '*'. It is a security risk")
      val.append('*')
  except Exception:
    LOG.warning("allowed_hosts value to '*'. It is a security risk")
    val.append('*')
  return val


def coerce_positive_integer(integer):
  integer = int(integer)

  if integer <= 0:
    raise Exception('integer is not positive')

  return integer


def is_lb_enabled():
  """Check for Hue Load Balancer is available"""
  return bool(HUE_LOAD_BALANCER.get())


def coerce_zero_or_positive_integer(integer):
  integer = int(integer)

  if integer < 0:
    raise Exception('integer is negative')

  return integer


def is_https_enabled():
  """Hue is configured for HTTPS."""
  return bool(SSL_CERTIFICATE.get() and SSL_PRIVATE_KEY.get())


def get_slack_client_id():
  """Returns Slack Client ID if set as environment variable else None"""
  return os.environ.get('SLACK_CLIENT_ID')


def get_slack_client_secret():
  """Returns Slack Client Secret if set as environment variable else None"""
  return os.environ.get('SLACK_CLIENT_SECRET')


def get_slack_verification_token():
  """Returns Slack Verification Token if set as environment variable else None"""
  return os.environ.get('SLACK_VERIFICATION_TOKEN')


def get_slack_bot_user_token():
  """Returns Slack Bot User Token if set as environment variable else None"""
  return os.environ.get('SLACK_BOT_USER_TOKEN')


def is_python2():
  """Hue is running on Python 2."""
  return sys.version_info[0] == 2


def is_custom_jwt_auth_enabled():
  """Returns True if key server url is set else returns False"""
  return bool(AUTH.JWT.KEY_SERVER_URL.get())


USE_CHERRYPY_SERVER = Config(
  key="use_cherrypy_server",
  help=_("If set to true, CherryPy will be used. Otherwise, Gunicorn will be used as the webserver."),
  type=coerce_bool,
  dynamic_default=is_python2)

GUNICORN_WORKER_CLASS = Config(
  key="gunicorn_work_class",
  help=_("Gunicorn work class: gevent or evenlet, gthread or sync."),
  type=str,
  default="gthread")

GUNICORN_NUMBER_OF_WORKERS = Config(
  key="gunicorn_number_of_workers",
  help=_("The number of Gunicorn worker processes. If not specified, it uses: (number of CPU * 2) + 1."),
  type=int,
  default=1)

GUNICORN_WORKER_TIMEOUT = Config(
  key="gunicorn_worker_timeout",
  help=_("Workers silent for more than this many seconds are killed and restarted."),
  type=int,
  default=900)

GUNICORN_WORKER_GRACEFUL_TIMEOUT = Config(
  key="gunicorn_worker_graceful_timeout",
  help=_("After receiving a restart signal, workers have this much time to finish serving requests. "
         "Workers still alive after the timeout (starting from the receipt of the restart signal) are force killed."),
  type=int,
  default=900)


LOG_LISTENER_SOCKET_NAME = Config(
  key="log_listener_socket_name",
  help=_("Name of the Unix Domain Socket file for log listener communication."),
  type=str,
  default="hue.uds")

HTTP_HOST = Config(
  key="http_host",
  help=_("HTTP host to bind to."),
  type=str,
  default="0.0.0.0")

HTTP_PORT = Config(
  key="http_port",
  help=_("HTTP port to bind to."),
  type=int,
  default=8888)

ENABLE_IPV6 = Config(
  key="enable_ipv6",
  help=_("Enable IPv6 support. If true, Hue will attempt to bind to IPv6 addresses."),
  type=coerce_bool,
  default=False)

HTTP_ALLOWED_METHODS = Config(
  key="http_allowed_methods",
  help=_("HTTP methods the server will be allowed to service."),
  type=coerce_csv,
  private=True,
  default=['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT'])

HUE_LOAD_BALANCER = Config(
  key="hue_load_balancer",
  help=_("A comma-separated list of available Hue load balancers."),
  type=coerce_csv,
  default=[]
)

X_FRAME_OPTIONS = Config(
  key="http_x_frame_options",
  help=_("X-Frame-Options HTTP header value."),
  type=str,
  default="SAMEORIGIN")

HUE_IMAGE_VERSION = Config(
  key="hue_image_version",
  help=_("Image version of Hue"),
  type=str,
  default="")

HUE_HOST_NAME = Config(
  key="hue_host",
  help=_("Name of Hue host selected"),
  type=str,
  default="")

LIMIT_REQUEST_FIELD_SIZE = Config(
  key="limit_request_field_size",
  help=_("This property specifies the maximum allowed size of an HTTP request header field."),
  default=8190,
  type=int
)

LIMIT_REQUEST_FIELDS = Config(
  key="limit_request_fields",
  help=_("This property specifies the maximum number of HTTP headers fields in a request."),
  default=100,
  type=int
)

LIMIT_REQUEST_LINE = Config(
  key="limit_request_line",
  help=_("This property specifies the maximum size of HTTP request line in bytes."),
  default=4094,
  type=int
)

SSL_CERTIFICATE = Config(
  key="ssl_certificate",
  help=_("Filename of SSL Certificate"),
  type=coerce_file,
  default=None)

SSL_PRIVATE_KEY = Config(
  key="ssl_private_key",
  help=_("Filename of SSL RSA Private Key"),
  type=coerce_file,
  default=None)

SSL_CERTIFICATE_CHAIN = Config(
  key="ssl_certificate_chain",
  help=_("Filename of SSL Certificate Chain"),
  type=coerce_file,
  default=None)

SSL_CIPHER_LIST = Config(
  key="ssl_cipher_list",
  help=_("List of allowed and disallowed ciphers for TLS 1.2"),

  # Based on "Intermediate compatibility" recommendations from
  # https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28recommended.29
  # which should be compatible with Firefox 27, Chrome 31, IE 11,
  # Opera 20 and Safari 9.
  default=':'.join([
    'ECDHE-RSA-AES128-GCM-SHA256',
    'ECDHE-RSA-AES256-GCM-SHA384',
    'ECDHE-ECDSA-AES128-GCM-SHA256',
    'ECDHE-ECDSA-AES256-GCM-SHA384',
    'DHE-RSA-AES128-GCM-SHA256',
    'DHE-RSA-AES256-GCM-SHA384',
    '!aNULL',
    '!eNULL',
    '!EXPORT',
    '!DES',
    '!RC4',
    '!MD5',
    '!PSK',
    '!aECDH',
    '!EDH-DSS-DES-CBC3-SHA',
    '!EDH-RSA-DES-CBC3-SHA',
    '!KRB5-DES-CBC3-SHA',
  ]))


# TLS 1.3 Configuration
def has_tls13_support():
  """Check if TLS 1.3 is supported by the current Python/OpenSSL version."""
  try:
    import ssl
    return hasattr(ssl, 'HAS_TLSv1_3') and ssl.HAS_TLSv1_3
  except ImportError:
    return False


SSL_TLS13_ENABLED = Config(
  key="ssl_tls13_enabled",
  help=_("Enable TLS 1.3 support when available. Requires OpenSSL 1.1.1+ and Python 3.7+"),
  type=coerce_bool,
  dynamic_default=has_tls13_support)

SSL_TLS12_ENABLED = Config(
  key="ssl_tls12_enabled",
  help=_("Enable TLS 1.2 support when available. This is the default behavior."),
  type=coerce_bool,
  default=True)


def has_ssl_no_renegotiation():
  return sys.version_info[:2] >= (3, 7)


SSL_NO_RENEGOTIATION = Config(
  key="ssl_no_renegotiation",
  type=coerce_bool,
  default=has_ssl_no_renegotiation(),
  help=_(
    "Disable all renegotiation in TLSv1.2 and earlier. Do not send HelloRequest messages, and ignore renegotiation requests"
    " via ClientHello. This option is only available with OpenSSL 1.1.0h and later and python 3.7")
  )

SSL_PASSWORD = Config(
  key="ssl_password",
  help=_("SSL password of the certificate"),
  default=None)

SSL_PASSWORD_SCRIPT = Config(
  key="ssl_password_script",
  help=_("Execute this script to produce the SSL password. This will be used when `ssl_password` is not set."),
  type=coerce_password_from_script,
  default=None)

SSL_CACERTS = Config(
  key="ssl_cacerts",
  help=_('Path to default Certificate Authority certificates.'),
  type=str,
  default='')

SSL_VALIDATE = Config(
  key="ssl_validate",
  help=_('Choose whether Hue should validate certificates received from the server.'),
  type=coerce_bool,
  default=True)

SECURE_HSTS_SECONDS = Config(
  key="secure_hsts_seconds",
  help=_('Strict-Transport-Security: max-age=31536000 This is a HTTP response header. '
  'Once a supported browser receives this header that browser will prevent any communications from being sent over HTTP to the '
  'specified domain and will instead send all communications over HTTPS.'),
  type=int,
  default=31536000)

SECURE_HSTS_INCLUDE_SUBDOMAINS = Config(
  key="secure_hsts_include_subdomains",
  help=_('Strict-Transport-Security: This is a HTTP response header. '
  'Once a supported browser receives this header that browser will prevent any communications from being sent over HTTP to the '
  'specified domain and will instead send all communications over HTTPS.'),
  type=coerce_bool,
  default=True)

SECURE_CONTENT_TYPE_NOSNIFF = Config(
  key="secure_content_type_nosniff",
  help=_('X-Content-Type-Options: nosniff. This is a HTTP response header feature that helps prevent attacks '
  'based on MIME-type confusion.'),
  type=coerce_bool,
  default=True)

SECURE_BROWSER_XSS_FILTER = Config(
  key="secure_browser_xss_filter",
  help=_('X-Xss-Protection: \"1; mode=block\" This is a HTTP response header feature to force XSS protection.'),
  type=coerce_bool,
  default=True)

SECURE_CONTENT_SECURITY_POLICY = Config(
  key="secure_content_security_policy",
  help=_('X-Content-Type-Options: nosniff. This is a HTTP response header feature that helps prevent attacks '
    'based on MIME-type confusion.'),
  type=str,
  default="script-src 'self' 'unsafe-inline' 'unsafe-eval' *.googletagmanager.com *.doubleclick.net data:;" +
          "img-src 'self' *.doubleclick.net http://*.tile.osm.org *.tile.osm.org *.gstatic.com data:;" +
          "style-src 'self' 'unsafe-inline' fonts.googleapis.com;" +
          "connect-src 'self' *.google-analytics.com;" +
          "frame-src *;" +
          "child-src 'self' data: *.vimeo.com;" +
          "object-src 'none'")

CSP_NONCE = Config(
  key="csp_nonce",
  help=_('Generates a unique nonce for each request to strengthen CSP by disallowing '
        '"unsafe-inline" scripts and styles.'),
  type=coerce_bool,
  default=False)

SECURE_SSL_REDIRECT = Config(
  key="secure_ssl_redirect",
  help=_('If all non-SSL requests should be permanently redirected to SSL.'),
  type=coerce_bool,
  dynamic_default=is_https_enabled)

SECURE_SSL_HOST = Config(
  key="secure_redirect_host",
  help=_('If all non-SSL requests should be permanently redirected to this SSL host.'),
  type=str,
  default="0.0.0.0")

SECURE_REDIRECT_EXEMPT = Config(
  key="secure_redirect_exempt",
  help=_('Comma separated list of strings representing the host/domain names that the Hue server can not serve https.'),
  type=coerce_csv,
  default=[])

# Deprecated by AUTH_PASSWORD
LDAP_PASSWORD = Config(
  key="ldap_password",
  help=_("LDAP password of the hue user used for LDAP authentications. For example for LDAP Authentication with HiveServer2/Impala."),
  private=True,
  default=None)

# Deprecated by AUTH_PASSWORD_SCRIPT
LDAP_PASSWORD_SCRIPT = Config(
  key="ldap_password_script",
  help=_("Execute this script to produce the LDAP password. This will be used when `ldap_password` is not set."),
  private=True,
  type=coerce_password_from_script,
  default=None)

# Deprecated by by AUTH_USERNAME
LDAP_USERNAME = Config(
  key="ldap_username",
  help=_("LDAP username of the hue user used for LDAP authentications. For example for LDAP Authentication with HiveServer2/Impala."),
  private=True,
  default="hue")


def get_auth_username():
  """Backward compatibility"""
  return LDAP_USERNAME.get()


AUTH_USERNAME = Config(
  key="auth_username",
  help=_("Auth username of the hue user used for authentications. For example for LDAP Authentication with HiveServer2/Impala."),
  dynamic_default=get_auth_username)


def get_auth_password():
  """Get from script or backward compatibility"""

  password = os.environ.get('HUE_AUTH_PASSWORD')
  if password is not None:
    return password

  password = AUTH_PASSWORD_SCRIPT.get()
  if password:
    return password

  password = os.environ.get('HUE_LDAP_PASSWORD')
  if password is not None:
    return password

  password = LDAP_PASSWORD.get()  # 2 levels for backward compatibility
  if password:
    return password

  return LDAP_PASSWORD_SCRIPT.get()


AUTH_PASSWORD = Config(
  key="auth_password",
  help=_("LDAP/PAM/.. password of the hue user used for authentications. Inactive if empty. "
  "For example for LDAP Authentication with HiveServer2/Impala."),
  private=True,
  dynamic_default=get_auth_password)

AUTH_PASSWORD_SCRIPT = Config(
  key="auth_password_script",
  help=_("Execute this script to produce the auth password. This will be used when `auth_password` is not set."),
  private=True,
  type=coerce_password_from_script,
  default=None)

ENABLE_SERVER = Config(
  key="enable_server",
  help=_("If set to false, runcpserver will not actually start the web server.  Used if Apache is being used as a WSGI container."),
  type=coerce_bool,
  default=True)

CHERRYPY_SERVER_THREADS = Config(
  key="cherrypy_server_threads",
  help=_("Number of threads used by the CherryPy web server."),
  type=int,
  default=50)

SECRET_KEY = Config(
  key="secret_key",
  help=_("Used in hashing algorithms for sessions."),
  default="")

SECRET_KEY_SCRIPT = Config(
  key="secret_key_script",
  help=_("Execute this script to produce the Django secret key. This will be used when `secret_key` is not set."),
  type=coerce_password_from_script,
  private=True,
  default="")

USER_ACCESS_HISTORY_SIZE = Config(
  key="user_access_history_size",
  help=_("Number of user access to remember per view per user."),
  type=int,
  default=10)

COLLECT_USAGE = Config(
  key="collect_usage",
  help=_("Help improve Hue with anonymous usage analytics."
         "Use Google Analytics 4 to see how many times an application or specific section of an application is used, nothing more."),
  type=coerce_bool,
  default=True)

GTAG_ID = Config(
  key="gtag_id",
  help=_("The gTag id used for anonymous analytics with Google Analytics 4."),
  type=str,
  default="G-25K7599S1Q")

REST_RESPONSE_SIZE = Config(
  key="rest_response_size",
  help=_("Number of characters the rest api reponse calls to dump to the logs when debug is enabled.  Enter -1 for entire response."),
  type=int,
  default=2000)

LEAFLET_TILE_LAYER = Config(
  key="leaflet_tile_layer",
  help=_("Tile layer server URL for the Leaflet map charts. Read more on http://leafletjs.com/reference.html#tilelayer. "
  "Make sure you add the tile domain to the img-src section of the 'secure_content_security_policy' configuration parameter as well."),
  type=str,
  default="http://{s}.tile.osm.org/{z}/{x}/{y}.png")

LEAFLET_TILE_LAYER_ATTRIBUTION = Config(
  key="leaflet_tile_layer_attribution",
  help=_("The copyright message for the specified Leaflet maps Tile Layer"),
  default='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors')

LEAFLET_MAP_OPTIONS = Config(
  key="leaflet_map_options",
  help=_("All the map options, accordingly to http://leafletjs.com/reference-0.7.7.html#map-options. "
  "To change CRS, just use the name, ie. 'EPSG4326'"),
  type=coerce_json_dict,
  default="{}")

LEAFLET_TILE_LAYER_OPTIONS = Config(
  key="leaflet_tile_layer_options",
  help=_("All the tile layer options, accordingly to http://leafletjs.com/reference-0.7.7.html#tilelayer"),
  type=coerce_json_dict,
  default="{}")

POLL_ENABLED = Config(
  key="poll_enabled",
  help=_("Use poll(2) in Hue thrift pool."),
  type=coerce_bool,
  private=True,
  default=True)

MIDDLEWARE = Config(
  key="middleware",
  help=_("Comma-separated list of Django middleware classes to use. " +
         "See https://docs.djangoproject.com/en/1.4/ref/middleware/ for " +
         "more details on middlewares in Django."),
  type=coerce_csv,
  default=[])

REDIRECT_WHITELIST = Config(
  key="redirect_whitelist",
  help=_("Comma-separated list of regular expressions, which match the redirect URL."
         "For example, to restrict to your local domain and FQDN, the following value can be used:"
         r"  ^\/.*$,^http:\/\/www.mydomain.com\/.*$"),
  type=list_of_compiled_res(skip_empty=True),
  default=r'^(\/[a-zA-Z0-9]+.*|\/)$')

USE_X_FORWARDED_HOST = Config(
  key="use_x_forwarded_host",
  help=_("Enable X-Forwarded-Host header if the load balancer requires it."),
  type=coerce_bool,
  dynamic_default=is_lb_enabled)

ENABLE_XFF_FOR_HIVE_IMPALA = Config(
  key="enable_xff_for_hive_impala",
  help=_("Enable X-Forwarded-For header if the hive/impala requires it."),
  type=coerce_bool,
  default=False)

ENABLE_X_CSRF_TOKEN_FOR_HIVE_IMPALA = Config(
  key="enable_x_csrf_token_for_hive_impala",
  help=_("Enable X_CSRF_TOKEN header if the hive/impala requires it."),
  type=coerce_bool,
  default=False)

SECURE_PROXY_SSL_HEADER = Config(
  key="secure_proxy_ssl_header",
  help=_("Support for HTTPS termination at the load-balancer level with SECURE_PROXY_SSL_HEADER."),
  type=coerce_bool,
  default=False)

APP_BLACKLIST = Config(
  key='app_blacklist',
  default='',
  type=coerce_csv,
  help=_('Comma separated list of apps to not load at server startup.')
)

CLUSTER_ID = Config(
  key="cluster_id",
  help=_("Id of the cluster where Hue is located."),
  private=False,
  default='default')

DEMO_ENABLED = Config(  # Internal and Temporary
  key="demo_enabled",
  help=_("To set to true in combination when using Hue demo backend."),
  type=coerce_bool,
  private=True,
  default=False)

LOG_REDACTION_FILE = Config(
  key="log_redaction_file",
  help=_("Use this file to parse and redact log message."),
  type=parse_redaction_policy_from_file,
  default=None)

ALLOWED_HOSTS = Config(
  key='allowed_hosts',
  # dynamic_default=get_dn, # Currently off as pretty disruptive
  default=['*'],
  type=coerce_csv,
  help=_('Comma separated list of strings representing the host/domain names that the Hue server can serve.')
)

REST_CONN_TIMEOUT = Config(
  key='rest_conn_timeout',
  default=120,
  type=int,
  help=_('Timeout in seconds for REST calls.'))


VCS = UnspecifiedConfigSection(
  "vcs",
  help="One entry for each Version Control",
  each=ConfigSection(
    help="""Configuration options for source version control used to list and
            save files from the editor. Example: Git, SVN""",
    members=dict(
      REMOTE_URL=Config(
        key="remote_url",
        help=_("Base URL to Interface Remote Server"),
        default='https://github.com/cloudera/hue/tree/master',
        type=coerce_string,
      ),
      API_URL=Config(
        key="api_url",
        help=_("Base URL to Interface API"),
        default='https://api.github.com',
        type=coerce_string,
      ),
      CLIENT_ID=Config(
        key="client_id",
        help=_("The Client ID of the Interface application."),
        type=coerce_string,
        default=""
      ),
      CLIENT_SECRET=Config(
        key="client_secret",
        help=_("The Client Secret of the Interface application."),
        type=coerce_string,
        default=""
      )
    )
  )
)


def default_secure_cookie():
  """Enable secure cookies if HTTPS is enabled."""
  return is_https_enabled()


def default_ssl_cacerts():
  """Path to default Certificate Authority certificates"""
  return SSL_CACERTS.get()


def default_ssl_validate():
  """Choose whether Hue should validate certificates received from the server."""
  return SSL_VALIDATE.get()


#
# Email (SMTP) settings
#


_default_from_email = None


def default_from_email():
  """Email for hue@<host-fqdn>"""
  global _default_from_email
  if _default_from_email is None:
    try:
      fqdn = socket.getfqdn()
    except IOError:
      fqdn = 'localhost'
    _default_from_email = "hue@" + fqdn
  return _default_from_email


def default_database_options():
  """Database type dependent options"""
  if DATABASE.ENGINE.get().endswith('oracle'):
    return {'threaded': True}
  elif DATABASE.ENGINE.get().endswith('sqlite3'):
    return {'timeout': 30}
  else:
    return {}


def get_deprecated_login_lock_out_by_combination_browser_user_agent():
  """Return value of deprecated LOGIN_LOCK_OUT_BY_COMBINATION_BROWSER_USER_AGENT_AND_IP config"""
  return AUTH.LOGIN_LOCK_OUT_BY_COMBINATION_BROWSER_USER_AGENT_AND_IP.get()


SMTP = ConfigSection(
  key='smtp',
  help=_('Configuration options for connecting to an external SMTP server.'),
  members=dict(
    HOST=Config(
      key="host",
      help=_("The SMTP server for email notification delivery."),
      type=str,
      default="localhost"
    ),
    PORT=Config(
      key="port",
      help=_("The SMTP server port."),
      type=int,
      default=25
    ),
    USER=Config(
      key="user",
      help=_("The username for the SMTP host."),
      type=str,
      default=""
    ),
    PASSWORD=Config(
      key="password",
      help=_("The password for the SMTP user."),
      type=str,
      private=True,
      default="",
    ),
    PASSWORD_SCRIPT=Config(
      key="password_script",
      help=_("Execute this script to produce the SMTP user password. This will be used when the SMTP `password` is not set."),
      type=coerce_password_from_script,
      private=True,
      default="",
    ),
    USE_TLS=Config(
      key="tls",
      help=_("Whether to use a TLS (secure) connection when talking to the SMTP server."),
      type=coerce_bool,
      default=False
    ),
    DEFAULT_FROM=Config(
      key="default_from_email",
      help=_("Default email address to use for various automated notifications from Hue."),
      type=str,
      dynamic_default=default_from_email
    ),
  )
)

METRICS = ConfigSection(
  key='metrics',
  help=_("""Configuration options for metrics"""),
  members=dict(
    ENABLE_WEB_METRICS=Config(
      key='enable_web_metrics',
      help=_('Enable metrics URL "desktop/metrics"'),
      default=True,
      type=coerce_bool),
    LOCATION=Config(
      key='location',
      help=_('If specified, Hue will write metrics to this file'),
      type=str),
    COLLECTION_INTERVAL=Config(
      key='collection_interval',
      help=_('Time in milliseconds on how frequently to collect metrics'),
      type=coerce_positive_integer,
      default=30000),
  )
)


def is_gunicorn_report_enabled():
  return 'rungunicornserver' in sys.argv \
    and METRICS.LOCATION.get() is not None \
    and METRICS.COLLECTION_INTERVAL.get() is not None


SLACK = ConfigSection(
  key='slack',
  help=_("""Configuration options for slack """),
  members=dict(
    IS_ENABLED=Config(
      key='is_enabled',
      help=_('Enables Slack application API endpoints'),
      type=coerce_bool,
      default=False,
    ),
    SLACK_CLIENT_ID=Config(
      key='slack_client_id',
      type=str,
      dynamic_default=get_slack_client_id,
    ),
    SLACK_CLIENT_SECRET=Config(
      key='slack_client_secret',
      type=str,
      private=True,
      dynamic_default=get_slack_client_secret,
    ),
    SLACK_VERIFICATION_TOKEN=Config(
      key='slack_verification_token',
      type=str,
      private=True,
      dynamic_default=get_slack_verification_token,
    ),
    SLACK_BOT_USER_TOKEN=Config(
      key='slack_bot_user_token',
      type=str,
      private=True,
      dynamic_default=get_slack_bot_user_token,
    ),
    SHARE_FROM_EDITOR=Config(
      key='share_from_editor',
      help=_('Enables direct sharing from Editor to Slack'),
      type=coerce_bool,
      default=True,
    ),
  )
)

ANALYTICS = ConfigSection(
  key='analytics',
  help=_("""Configuration options for analytics user usage for admins"""),
  members=dict(
    IS_ENABLED=Config(
      key='is_enabled',
      help=_('Enable analytics page'),
      default=False,
      type=coerce_bool),
  )
)


DATABASE = ConfigSection(
  key='database',
  help=_("""Configuration options for specifying the Desktop Database.
          For more info, see http://docs.djangoproject.com/en/1.4/ref/settings/#database-engine"""),
  members=dict(
    ENGINE=Config(
      key='engine',
      help=_('Database engine, such as postgresql_psycopg2, mysql, or sqlite3.'),
      type=coerce_database,
      default='django.db.backends.sqlite3',
    ),
    NAME=Config(
      key='name',
      help=_('Database name, or path to DB if using sqlite3.'),
      type=str,
      default=get_desktop_root('desktop.db'),
    ),
    USER=Config(
      key='user',
      help=_('Database username.'),
      type=str,
      default='',
    ),
    SCHEMA=Config(
      key='schema',
      help=_('Database schema, to be used only when public schema is revoked in postgres.'),
      type=str,
      default='public',
    ),
    PASSWORD=Config(
      key='password',
      help=_('Database password.'),
      private=True,
      type=str,
      default="",
    ),
    PASSWORD_SCRIPT=Config(
      key='password_script',
      help=_('Execute this script to produce the database password. This will be used when `password` is not set.'),
      private=True,
      type=coerce_password_from_script,
      default="",
    ),
    HOST=Config(
      key='host',
      help=_('Database host.'),
      type=str,
      default='',
    ),
    PORT=Config(
      key='port',
      help=_('Database port.'),
      type=coerce_port,
      default='',
    ),
    OPTIONS=Config(
      key='options',
      help=_('Database options to send to the server when connecting.'),
      type=coerce_json_dict,
      dynamic_default=default_database_options
    ),
    CONN_MAX_AGE=Config(
      key='conn_max_age',
      help=_('The CONN_MAX_AGE parameter controls db connections persistency in seconds.'),
      type=coerce_zero_or_positive_integer,
      default=0,
    )
  )
)

SESSION = ConfigSection(
  key='session',
  help=_("""Configuration options for specifying the Desktop session.
          For more info, see https://docs.djangoproject.com/en/1.4/topics/http/sessions/"""),
  members=dict(
    COOKIE_NAME=Config(
      key='cookie_name',
      help=_("The name of the cookie to use for sessions."
            "This can have any value that is not used by the other cookie names in your application."),
      type=str,
      default="sessionid",
    ),
    ENABLE_TEST_COOKIE=Config(
      key='enable_test_cookie',
      help=_("Configuration to determine whether test cookie should be added determine whether the user's browser supports cookies."),
      type=coerce_bool,
      default=True,
    ),
    TTL=Config(
      key='ttl',
      help=_("The cookie containing the users' session ID will expire after this amount of time in seconds."),
      type=int,
      default=60 * 60 * 24 * 14,
    ),
    SECURE=Config(
      key='secure',
      help=_("The cookie containing the users' session ID will be secure. This should only be enabled with HTTPS."),
      type=coerce_bool,
      dynamic_default=default_secure_cookie,
    ),
    HTTP_ONLY=Config(
      key='http_only',
      help=_("The cookie containing the users' session ID will use the HTTP only flag."),
      type=coerce_bool,
      default=True,
    ),
    EXPIRE_AT_BROWSER_CLOSE=Config(
      key='expire_at_browser_close',
      help=_("Use session-length cookies. Logs out the user when she closes the browser window."),
      type=coerce_bool,
      default=True
    ),
    CSRF_COOKIE_AGE=Config(
      key='csrf_cookie_age',
      help=_("CRSF cookie age defaults to 1 year. If the value is set to 0, it means per session. Time in seconds"),
      type=int,
      default=0,
    ),
    CONCURRENT_USER_SESSION_LIMIT=Config(
      key="concurrent_user_session_limit",
      help=_("If set, limits the number of concurrent user sessions. 1 represents 1 session per user. "
        "Default: 0 (unlimited sessions per user)"),
      type=int,
      default=0,
    ),
    TRUSTED_ORIGINS=Config(
      key="trusted_origins",
      help=_("A list of hosts which are trusted origins for unsafe requests. See django's CSRF_TRUSTED_ORIGINS for more information"),
      type=coerce_csv,
      default='.cloudera.com',
    )
  )
)

KNOX = ConfigSection(
  key="knox",
  help=_("""Configuration options for specifying Hue's KNOX integration for secured Hadoop clusters."""),
  members=dict(
    KNOX_PRINCIPAL=Config(
      key='knox_principal',
      help=_("Comma separated list of Kerberos principal name for Hue. Typically 'knox/hostname.foo.com'."),
      type=coerce_csv,
      default="knox/%s" % socket.getfqdn()),
    KNOX_PROXYHOSTS=Config(
      key='knox_proxyhosts',
      default="%s" % socket.getfqdn(),
      type=coerce_csv,
      help=_('Comma separated list of strings representing the host names that the Hue server can trust as knox hosts.')
    ),
    KNOX_PORTS=Config(
      key='knox_ports',
      default=['80', '8443'],
      type=coerce_csv,
      help=_('Comma separated list of strings representing the ports that the Hue server can trust as knox port.')
    ),
  )
)

KERBEROS = ConfigSection(
  key="kerberos",
  help=_("""Configuration options for specifying Hue's Kerberos integration for
          secured Hadoop clusters."""),
  members=dict(
    HUE_KEYTAB=Config(
      key='hue_keytab',
      help=_("Path to a Kerberos keytab file containing Hue's service credentials."),
      type=str,
      default=None),
    HUE_PRINCIPAL=Config(
      key='hue_principal',
      help=_("Kerberos principal name for Hue. Typically 'hue/hostname.foo.com'."),
      type=str,
      default="hue/%s" % socket.getfqdn()),
    REINIT_FREQUENCY=Config(
      key='reinit_frequency',
      help=_("Frequency in seconds with which Hue will renew its keytab."),
      type=int,
      default=60 * 60),  # 1h
    KT_RENEWER_TIMEOUT=Config(
      key='kt_renewer_timeout',
      help=_("kt-renewer sleep timeout in seconds"),
      type=int,
      default=10),  # 10 secs
    CCACHE_PATH=Config(
      key='ccache_path',
      help=_("Path to keep Kerberos credentials cached."),
      private=True,
      type=str,
      default="/var/run/hue/hue_krb5_ccache",
    ),
    ENABLE_RENEWLIFETIME=Config(
      key='krb5_renewlifetime_enabled',
      help=_("set this to false if renew_lifetime in krb5.conf is set to 0m"),
      type=coerce_bool,
      default=True,
    ),
    KINIT_PATH=Config(
      key='kinit_path',
      help=_("Path to Kerberos 'kinit' command."),
      type=str,
      default="kinit",  # use PATH!
    ),
    MUTUAL_AUTHENTICATION=Config(
      key='mutual_authentication',
      help=_('Mutual authentication from the server, attaches HTTP GSSAPI/Kerberos Authentication to the given Request object'),
      type=str,
      default="OPTIONAL",
    ),
  )
)

SASL_MAX_BUFFER = Config(
  key="sasl_max_buffer",
  help=_("This property specifies the maximum size of the receive buffer in bytes in thrift sasl communication."),
  default=2 * 1024 * 1024,  # 2 MB
  type=int
)

ENABLE_SMART_THRIFT_POOL = Config(
  key="enable_smart_thrift_pool",
  help=_("Hue will try to get the actual host of the Service, even if it resides behind a load balancer. "
         "This will enable an automatic configuration of the service without requiring custom configuration of the service load balancer. "
         "This is available for the Impala service only currently. "
         "It is highly recommended to only point to a series of coordinator-only nodes only."),
  type=coerce_bool,
  default=False
)


# See python's documentation for time.tzset for valid values.
TIME_ZONE = Config(
  key="time_zone",
  help=_("Time zone name."),
  type=str,
  default=os.environ.get("TZ", "America/Los_Angeles")
)

DEFAULT_SITE_ENCODING = Config(
  key='default_site_encoding',
  help=_('Default system-wide unicode encoding.'),
  type=str,
  default='utf-8'
)

SERVER_USER = Config(
  key="server_user",
  help=_("Username to run servers as."),
  type=str,
  default="hue")

SERVER_GROUP = Config(
  key="server_group",
  help=_("Group to run servers as."),
  type=str,
  default="hue")

DEFAULT_USER = Config(
  key="default_user",
  help=_("This should be the user running hue webserver"),
  type=str,
  default="hue")

DEFAULT_HDFS_SUPERUSER = Config(
  key="default_hdfs_superuser",
  help=_("This should be the hdfs super user"),
  type=str,
  default="hdfs")

CUSTOM = ConfigSection(
  key="custom",
  help=_("Customizations to the UI."),
  members=dict(
    BANNER_TOP_HTML=Config("banner_top_html",
                   default="",
                   help=_("Top banner HTML code. This code will be placed in the navigation bar "
                        "so that it will reside at the top of the page in a fixed position. " +
                        "One common value is `<img src=\"http://www.example.com/example.gif\" />`")),
    LOGIN_SPLASH_HTML=Config("login_splash_html",
                   default="",
                   help=_("The login splash HTML code. This code will be placed in the login page, "
                        "useful for security warning messages.")),
    CACHEABLE_TTL=Config("cacheable_ttl",
                   default=10 * 24 * 60 * 60 * 1000,
                   type=int,
                   help=_("The cache TTL in milliseconds for the assist/autocomplete/etc calls. Set to 0 it disables the cache.")),
    LOGO_SVG=Config("logo_svg",
                   default="",
                   help=_("SVG code to replace the default Hue logo in the top bar and sign in screen"))
))

AUTH = ConfigSection(
  key="auth",
  help=_("Configuration options for user authentication into the web application."),
  members=dict(
    BACKEND=Config("backend",
                   default=["desktop.auth.backend.AllowFirstUserDjangoBackend"],
                   type=coerce_csv,
                   help=_("Authentication backend.  Common settings are "
                        "django.contrib.auth.backends.ModelBackend (fully Django backend), " +
                        "desktop.auth.backend.AllowAllBackend (allows everyone), " +
                        "desktop.auth.backend.AllowFirstUserDjangoBackend (relies on Django and user manager, after the first login). " +
                        "Multiple Authentication backends are supported by specifying a comma-separated list in order of priority.")),
    USER_AUGMENTOR=Config("user_augmentor",
                   default="desktop.auth.backend.DefaultUserAugmentor",
                   help=_("Class which defines extra accessor methods for User objects.")),
    PAM_SERVICE=Config("pam_service",
                  default="login",
                  help=_("The service to use when querying PAM. "
                         "The service usually corresponds to a single filename in /etc/pam.d")),
    PAM_USE_PWD_MODULE=Config("pam_use_pwd_module",
                       help=_("To use Python unix pwd module to get the username from the entered credentials in hue"
                              " if Centrify like PAM service is in use. "
                              "This will set the username to what is being returned by the pwd module."),
                       type=coerce_bool,
                       default=False),
    REMOTE_USER_HEADER=Config("remote_user_header",
                        default="HTTP_REMOTE_USER",
                        help=_("When using the desktop.auth.backend.RemoteUserDjangoBackend, this sets "
                               "the normalized name of the header that contains the remote user. "
                               "The HTTP header in the request is converted to a key by converting "
                               "all characters to uppercase, replacing any hyphens with underscores "
                               "and adding an HTTP_ prefix to the name. So, for example, if the header "
                               "is called Remote-User that would be configured as HTTP_REMOTE_USER")),
    IGNORE_USERNAME_CASE=Config("ignore_username_case",
                                  help=_("Ignore the case of usernames when searching for existing users in Hue."),
                                  type=coerce_bool,
                                  default=True),
    FORCE_USERNAME_LOWERCASE=Config("force_username_lowercase",
                                      help=_("Force usernames to lowercase when creating new users."),
                                      type=coerce_bool,
                                      default=True),
    FORCE_USERNAME_UPPERCASE=Config("force_username_uppercase",
                                      help=_("Force usernames to uppercase when creating new users."),
                                      type=coerce_bool,
                                      default=False),
    EXPIRES_AFTER=Config("expires_after",
                            help=_("Users will expire after they have not logged in for 'n' amount of seconds."
                                   "A negative number means that users will never expire."),
                            type=int,
                            default=-1),
    EXPIRE_SUPERUSERS=Config("expire_superusers",
                                help=_("Apply 'expires_after' to superusers."),
                                type=coerce_bool,
                                default=True),
    IDLE_SESSION_TIMEOUT=Config("idle_session_timeout",
                            help=_("Users will automatically be logged out after 'n' seconds of inactivity."
                                   "A negative number means that idle sessions will not be timed out."),
                            type=int,
                            default=-1),
    CHANGE_DEFAULT_PASSWORD=Config(
                            key="change_default_password",
                            help=_("When set to true this will allow you to specify a password for "
                                   "the user when you create the user and then force them to change "
                                   "their password upon first login.  The default is false."),
                            type=coerce_bool,
                            default=False,
    ),
    LOGIN_FAILURE_LIMIT=Config(
      key="login_failure_limit",
      help=_("Number of login attempts allowed before a record is created for failed logins"),
      type=int,
      default=3,
    ),
    LOGIN_LOCK_OUT_AT_FAILURE=Config(
      key="login_lock_out_at_failure",
      help=_("After number of allowed login attempts are exceeded, do we lock out this IP and optionally user agent?"),
      type=coerce_bool,
      default=False,
    ),
    LOGIN_COOLOFF_TIME=Config(
      key="login_cooloff_time",
      help=_("If set, defines period of inactivity in hours after which failed logins will be forgotten."
             "A value of 0 or None will disable this check. Default: None."),
      type=coerce_timedelta,
      default=None,
    ),
    # Deprecated by LOGIN_LOCK_OUT_USE_USER_AGENT
    LOGIN_LOCK_OUT_BY_COMBINATION_BROWSER_USER_AGENT_AND_IP=Config(
      key="login_lock_out_by_combination_browser_user_agent_and_ip",
      help=_("If True, lock out based on IP and browser user agent"),
      type=coerce_bool,
      default=False,
    ),
    LOGIN_LOCK_OUT_USE_USER_AGENT=Config(
      key="login_lock_out_use_user_agent",
      help=_("If True, lock out based on an IP address AND a user agent."
             "This means requests from different user agents but from the same IP are treated differently."),
      type=coerce_bool,
      dynamic_default=get_deprecated_login_lock_out_by_combination_browser_user_agent
    ),
    LOGIN_LOCK_OUT_BY_COMBINATION_USER_AND_IP=Config(
      key="login_lock_out_by_combination_user_and_ip",
      help=_("If True, lock out based on IP and user"),
      type=coerce_bool,
      default=False,
    ),
    BEHIND_REVERSE_PROXY=Config(
      key="behind_reverse_proxy",
      help=_("If True, it will look for the IP address from the header defined at reverse_proxy_header."),
      type=coerce_bool,
      dynamic_default=is_lb_enabled,
    ),
    REVERSE_PROXY_HEADER=Config(
      key="reverse_proxy_header",
      help=_("If behind_reverse_proxy is True, it will look for the IP address from this header. Default: HTTP_X_FORWARDED_FOR"),
      type=str,
      default="HTTP_X_FORWARDED_FOR",
    ),
    AUTO_LOGIN_ENABLED=Config(
      key="auto_login_enabled",
      help=_("If True, will auto log any request as a `hue` user that needs to exist."),
      type=coerce_bool,
      default=False,
    ),
    API_AUTH=Config(
        "api_auth",
        default=[
          'rest_framework_simplejwt.authentication.JWTAuthentication',
          'rest_framework.authentication.SessionAuthentication'],
        type=coerce_csv,
        help=_("Multiple Authentication backends are supported by specifying a comma-separated list in order of priority.")
    ),
    JWT=ConfigSection(
      key="jwt",
      help=_("Configuration for Custom JWT Authentication."),
      members=dict(
        KEY_SERVER_URL=Config(
            key="key_server_url",
            default=None,
            type=coerce_string,
            help=_("Endpoint to fetch the public key from verification server.")
        ),
        ISSUER=Config(
          key="issuer",
          default=None,
          type=str,
          help=_("The identifier of the service issued the JWT")
        ),
        AUDIENCE=Config(
          key="audience",
          default=None,
          type=str,
          help=_("The identifier of the resource intend to access")
        ),
        USERNAME_HEADER=Config(
          key="username_header",
          default="sub",
          type=str,
          help=_("The JWT payload header containing the username.")
        ),
        VERIFY=Config(
            key="verify",
            default=True,
            type=coerce_bool,
            help=_("Verify custom JWT signature.")
        ),
      )
    ),
))


LDAP = ConfigSection(
  key="ldap",
  help=_("Configuration options for LDAP connectivity."),
  members=dict(
    CREATE_USERS_ON_LOGIN=Config("create_users_on_login",
      help=_("Create users when they login with their LDAP credentials."),
      type=coerce_bool,
      default=True),
    SYNC_GROUPS_ON_LOGIN=Config("sync_groups_on_login",
      help=_("Synchronize a users groups when they login."),
      type=coerce_bool,
      default=True),
    IGNORE_USERNAME_CASE=Config("ignore_username_case",
      help=_("Ignore the case of usernames when searching for existing users in Hue."),
      type=coerce_bool,
      default=True),
    FORCE_USERNAME_LOWERCASE=Config("force_username_lowercase",
      help=_("Force usernames to lowercase when creating new users from LDAP."),
      type=coerce_bool,
      default=True),
    FORCE_USERNAME_UPPERCASE=Config("force_username_uppercase",
      help=_("Force usernames to uppercase when creating new users from LDAP."),
      type=coerce_bool,
      default=False),
    SUBGROUPS=Config("subgroups",
      help=_("Choose which kind of subgrouping to use: nested or suboordinate (deprecated)."),
      type=coerce_str_lowercase,
      default="suboordinate"),
    NESTED_MEMBERS_SEARCH_DEPTH=Config("nested_members_search_depth",
      help=_("Define the number of levels to search for nested members."),
      type=int,
      default=10),
    FOLLOW_REFERRALS=Config("follow_referrals",
      help=_("Whether or not to follow referrals."),
      type=coerce_bool,
      default=False),
    LOGIN_GROUPS=Config("login_groups",
      help=_("A comma-separated list of Ldap groups with users that can login"),
      type=coerce_csv,
      default=[]),
    DEBUG=Config("debug",
      type=coerce_bool,
      default=False,
      help=_("Set to a value to enable python-ldap debugging.")),
    DEBUG_LEVEL=Config("debug_level",
      default=255,
      type=int,
      help=_("Sets the debug level within the underlying LDAP C lib.")),
    TRACE_LEVEL=Config("trace_level",
      default=0,
      type=int,
      help=_("Possible values for trace_level are 0 for no logging, 1 for only logging the method calls with arguments,"
             "2 for logging the method calls with arguments and the complete results and "
             "9 for also logging the traceback of method calls.")),
    LDAP_SERVERS=UnspecifiedConfigSection(
      key="ldap_servers",
      help=_("LDAP server record."),
      each=ConfigSection(
        members=dict(
          BASE_DN=Config("base_dn",
                         default=None,
                         help=_("The base LDAP distinguished name to use for LDAP search.")),
          NT_DOMAIN=Config("nt_domain",
                           default=None,
                           help=_("The NT domain used for LDAP authentication.")),
          LDAP_URL=Config("ldap_url",
                           default=None,
                           help=_("The LDAP URL to connect to.")),
          USE_START_TLS=Config("use_start_tls",
                               default=True,
                               type=coerce_bool,
                               help=_("Use StartTLS when communicating with LDAP server.")),
          LDAP_CERT=Config("ldap_cert",
                           default=None,
                           help=_("A PEM-format file containing certificates for the CA's that Hue will trust "
                           "for authentication over TLS. The certificate for the CA that signed the LDAP server certificate must "
                           "be included among these certificates. See more here http://www.openldap.org/doc/admin24/tls.html.")),
          LDAP_USERNAME_PATTERN=Config("ldap_username_pattern",
                                       default=None,
                                       help=_("A pattern to use for constructing LDAP usernames.")),
          BIND_DN=Config("bind_dn",
                         default=None,
                         help=_("The distinguished name to bind as, when importing from LDAP.")),
          BIND_PASSWORD=Config("bind_password",
                               default=None,
                               private=True,
                               help=_("The password for the bind user.")),
          BIND_PASSWORD_SCRIPT=Config("bind_password_script",
                                    default=None,
                                    private=True,
                                    type=coerce_password_from_script,
                                    help=_("Execute this script to produce the LDAP bind user password. "
                                      "This will be used when `bind_password` is not set.")),
          SEARCH_BIND_AUTHENTICATION=Config("search_bind_authentication",
                                            default=True,
                                            type=coerce_bool,
                                            help=_("Use search bind authentication.")),
          FOLLOW_REFERRALS=Config("follow_referrals",
                                    help=_("Whether or not to follow referrals."),
                                    type=coerce_bool,
                                    default=False),
          TEST_LDAP_USER=Config("test_ldap_user",
                           default=None,
                           help=_("The test user name to use for LDAP search.")),
          TEST_LDAP_GROUP=Config("test_ldap_group",
                            default=None,
                            help=_("The test group name to use for LDAP search.")),

          DEBUG=Config("debug",
            type=coerce_bool,
            default=False,
            help=_("Set to a value to enable python-ldap debugging.")),
          DEBUG_LEVEL=Config("debug_level",
            default=255,
            type=int,
            help=_("Sets the debug level within the underlying LDAP C lib.")),
          TRACE_LEVEL=Config("trace_level",
            default=0,
            type=int,
            help=_("Possible values for trace_level are 0 for no logging, 1 for only logging the method calls with arguments,"
                   "2 for logging the method calls with arguments and the complete results and 9 for also logging the traceback "
                   "of method calls.")),

          USERS=ConfigSection(
            key="users",
            help=_("Configuration for LDAP user schema and search."),
            members=dict(
              USER_FILTER=Config("user_filter",
                                 default="objectclass=*",
                                 help=_("A base filter for use when searching for users.")),
              USER_NAME_ATTR=Config("user_name_attr",
                                    default="sAMAccountName",
                                    help=_("The username attribute in the LDAP schema. "
                                         "Typically, this is 'sAMAccountName' for AD and 'uid' "
                                         "for other LDAP systems.")),
            )
          ),

          GROUPS=ConfigSection(
            key="groups",
            help=_("Configuration for LDAP group schema and search."),
            members=dict(
              GROUP_FILTER=Config("group_filter",
                                 default="objectclass=*",
                                 help=_("A base filter for use when searching for groups.")),
              GROUP_NAME_ATTR=Config("group_name_attr",
                                    default="cn",
                                    help=_("The group name attribute in the LDAP schema. "
                                        "Typically, this is 'cn'.")),
              GROUP_MEMBER_ATTR=Config("group_member_attr",
                                       default="member",
                                       help=_("The LDAP attribute which specifies the "
                                            "members of a group.")),
            ))))),

    # Every thing below here is deprecated and should be removed in an upcoming major release.
    BASE_DN=Config("base_dn",
                   default=None,
                   help=_("The base LDAP distinguished name to use for LDAP search.")),
    NT_DOMAIN=Config("nt_domain",
                     default=None,
                     help=_("The NT domain used for LDAP authentication.")),
    LDAP_URL=Config("ldap_url",
                     default=None,
                     help=_("The LDAP URL to connect to.")),
    USE_START_TLS=Config("use_start_tls",
                         default=True,
                         type=coerce_bool,
                         help=_("Use StartTLS when communicating with LDAP server.")),
    LDAP_CERT=Config("ldap_cert",
                     default=None,
                     help=_("A PEM-format file containing certificates for the CA's that Hue will trust for authentication "
                     "over TLS. The certificate for the CA that signed the LDAP server certificate must be included among "
                     "these certificates. See more here http://www.openldap.org/doc/admin24/tls.html.")),
    LDAP_USERNAME_PATTERN=Config("ldap_username_pattern",
                                 default=None,
                                 help=_("A pattern to use for constructing LDAP usernames.")),
    BIND_DN=Config("bind_dn",
                   default=None,
                   help=_("The distinguished name to bind as, when importing from LDAP.")),
    BIND_PASSWORD=Config("bind_password",
                   default=None,
                   private=True,
                   help=_("The password for the bind user.")),
    BIND_PASSWORD_SCRIPT=Config("bind_password_script",
                   default=None,
                   private=True,
                   type=coerce_password_from_script,
                   help=_("Execute this script to produce the LDAP bind user password. "
                   "This will be used when `bind_password` is not set.")),
    SEARCH_BIND_AUTHENTICATION=Config("search_bind_authentication",
                   default=True,
                   type=coerce_bool,
                   help=_("Use search bind authentication.")),
    TEST_LDAP_USER=Config("test_ldap_user",
                   default=None,
                   help=_("The test user name to use for LDAP search.")),
    TEST_LDAP_GROUP=Config("test_ldap_group",
                   default=None,
                   help=_("The test group name to use for LDAP search.")),

    USERS=ConfigSection(
      key="users",
      help=_("Configuration for LDAP user schema and search."),
      members=dict(
        USER_FILTER=Config("user_filter",
                           default="objectclass=*",
                           help=_("A base filter for use when searching for users.")),
        USER_NAME_ATTR=Config("user_name_attr",
                              default="sAMAccountName",
                              help=_("The username attribute in the LDAP schema. "
                                   "Typically, this is 'sAMAccountName' for AD and 'uid' "
                                   "for other LDAP systems.")),
      )),

    GROUPS=ConfigSection(
      key="groups",
      help=_("Configuration for LDAP group schema and search."),
      members=dict(
        GROUP_FILTER=Config("group_filter",
                           default="objectclass=*",
                           help=_("A base filter for use when searching for groups.")),
        GROUP_NAME_ATTR=Config("group_name_attr",
                              default="cn",
                              help=_("The group name attribute in the LDAP schema. "
                                  "Typically, this is 'cn'.")),
        GROUP_MEMBER_ATTR=Config("group_member_attr",
                                 default="member",
                                 help=_("The LDAP attribute which specifies the "
                                      "members of a group.")),
      ))))


OAUTH = ConfigSection(
  key='oauth',
  help=_('Configuration options for Oauth 1.0 authentication'),
  members=dict(
    CONSUMER_KEY=Config(
      key="consumer_key",
      help=_("The Consumer key of the application."),
      type=str,
      default="XXXXXXXXXXXXXXXXXXXXX"
    ),

    CONSUMER_SECRET=Config(
      key="consumer_secret",
      help=_("The Consumer secret of the application."),
      type=str,
      default="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    ),

    REQUEST_TOKEN_URL=Config(
      key="request_token_url",
      help=_("The Request token URL."),
      type=str,
      default="https://api.twitter.com/oauth/request_token"
    ),

    ACCESS_TOKEN_URL=Config(
      key="access_token_url",
      help=_("The Access token URL."),
      type=str,
      default="https://api.twitter.com/oauth/access_token"
    ),

    AUTHENTICATE_URL=Config(
      key="authenticate_url",
      help=_("The Authorize URL."),
      type=str,
      default="https://api.twitter.com/oauth/authorize"
    ),

  )
)

OIDC = ConfigSection(
  key='oidc',
  help=_('Configuration options for OpenID Connect authentication'),
  members=dict(
    OIDC_RP_CLIENT_ID=Config(
      key="oidc_rp_client_id",
      help=_("The client ID as relay party set in OpenID provider."),
      type=str,
      default="XXXXXXXXXXXXXXXXXXXXX"
    ),

    OIDC_RP_CLIENT_SECRET=Config(
      key="oidc_rp_client_secret",
      help=_("The client secret as relay party set in OpenID provider."),
      type=str,
      default="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    ),

    OIDC_OP_AUTHORIZATION_ENDPOINT=Config(
      key="oidc_op_authorization_endpoint",
      help=_("The OpenID provider authoriation endpoint."),
      type=str,
      default="https://keycloak.example.com/auth/realms/Cloudera/protocol/openid-connect/auth"
    ),

    OIDC_OP_TOKEN_ENDPOINT=Config(
      key="oidc_op_token_endpoint",
      help=_("The OpenID provider token endpoint."),
      type=str,
      default="https://keycloak.example.com/auth/realms/cloudera/protocol/openid-connect/token"
    ),

    AUTHENTICATE_URL=Config(
      key="authenticate_url",
      help=_("The Authorize URL."),
      type=str,
      default="https://api.twitter.com/oauth/authorize"
    ),

    OIDC_OP_USER_ENDPOINT=Config(
      key="oidc_op_user_endpoint",
      help=_("The OpenID provider user info endpoint."),
      type=str,
      default="https://keycloak.example.com/auth/realms/cloudera/protocol/openid-connect/userinfo"
    ),

    OIDC_RP_IDP_SIGN_KEY=Config(
      key="oidc_rp_idp_sign_key",
      help=_("The OpenID provider signing key in PEM or DER format."),
      type=str,
      default=None
    ),

    OIDC_OP_JWKS_ENDPOINT=Config(
      key="oidc_op_jwks_endpoint",
      help=_("The OpenID provider authoriation endpoint."),
      type=str,
      default="https://keycloak.example.com/auth/realms/Cloudera/protocol/openid-connect/certs"
    ),

    OIDC_VERIFY_SSL=Config(
      key="oidc_verify_ssl",
      help=_("Whether Hue as OpenID Connect client verify SSL cert."),
      type=coerce_bool,
      default=False
    ),

    OIDC_AUTH_REQUEST_EXTRA_PARAMS=Config(
      key="oidc_auth_request_extra_params",
      help=_("OIDC authentication request extra params."),
      type=coerce_json_dict,
      default='{}'
    ),

    LOGIN_REDIRECT_URL=Config(
      key="login_redirect_url",
      help=_("As relay party Hue URL path to redirect to after login."),
      type=str,
      default="https://localhost:8888/oidc/callback/"
    ),

    LOGOUT_REDIRECT_URL=Config(
      key="logout_redirect_url",
      help=_("The OpenID provider URL path to redirect to after logout."),
      type=str,
      default="https://keycloak.example.com/auth/realms/cloudera/protocol/openid-connect/logout"
    ),

    LOGIN_REDIRECT_URL_FAILURE=Config(
      key="login_redirect_url_failure",
      help=_("As relay party Hue URL path to redirect to after login."),
      type=str,
      default="https://localhost:8888/hue/oidc_failed/"
    ),

    CREATE_USERS_ON_LOGIN=Config(
      key="create_users_on_login",
      help=_("Create a new user from OpenID Connect on login if it doesn't exist."),
      type=coerce_bool,
      default=True
    ),

    OIDC_USERNAME_ATTRIBUTE=Config(
      key="oidc_username_attribute",
      help=_("The attribute to be used as username when creating and looking up the user."),
      type=str,
      default="preferred_username"
    ),

    SUPERUSER_GROUP=Config(
      key="superuser_group",
      help=_("The group of users will be created and updated as superuser."),
      type=str,
      default=""
    ),
  )
)

LOCAL_FILESYSTEMS = UnspecifiedConfigSection(
  key="local_filesystems",
  help=_("Paths on the local file system that users should be able to browse."),
  each=ConfigSection(
    members=dict(
      PATH=Config("path",
                  required=True,
                  help=_("The path on the local filesystem.")))))


def default_feedback_url():
  """A version-specific URL."""
  return "http://groups.google.com/a/cloudera.org/group/hue-user"


FEEDBACK_URL = Config(
  key="feedback_url",
  help=_("Link for 'feedback' tab."),
  type=str,
  dynamic_default=default_feedback_url
)

SEND_DBUG_MESSAGES = Config(
  key="send_dbug_messages",
  help=_("Whether to send debug messages from JavaScript to the server logs."),
  type=coerce_bool,
  default=False
)

CUSTOM_CACHE_CONTROL = Config(
  key="custom_cache_control",
  help=_("Flag to disable webpage caching."),
  type=coerce_bool,
  default=False
)

DATABASE_LOGGING = Config(
  key="database_logging",
  help=_("Enable or disable database debug mode."),
  type=coerce_bool,
  default=False
)

DJANGO_ADMINS = UnspecifiedConfigSection(
  key="django_admins",
  help=_("Administrators that should receive error emails."),
  each=ConfigSection(
    members=dict(
      NAME=Config("name",
                  required=True,
                  help=_("The full name of the admin.")),
      EMAIL=Config("email",
                   required=True,
                   help=_("The email address of the admin.")))))

DJANGO_DEBUG_MODE = Config(
  key="django_debug_mode",
  help=_("Enable or disable debug mode."),
  type=coerce_bool,
  default=True
)

DEV = Config("dev",
   type=coerce_bool,
   default=False,
   help=_("Enable development mode, where notably static files are not cached.")
)

DISPLAY_APP_SWITCHER = Config(
  key='display_app_switcher',
  help=_('Enable or disable the upper left app switcher menu.'),
  type=coerce_bool,
  default=False
)

APP_SWITCHER_ALTUS_BASE_URL = Config(
  key="app_switcher_altus_base_url",
  help=_("Optional altus base url to use for the app switcher."),
  type=str,
  default=''
)

APP_SWITCHER_MOW_BASE_URL = Config(
  key="app_switcher_mow_base_url",
  help=_("Optional mow base url to use for the app switcher."),
  type=str,
  default=''
)

HTTP_500_DEBUG_MODE = Config(
  key='http_500_debug_mode',
  help=_('Enable or disable debugging information in the 500 internal server error response. '
       'Note that the debugging information may contain sensitive data. '
       'If django_debug_mode is True, this is automatically enabled.'),
  type=coerce_bool,
  default=True
)


def get_instrumentation_default():
  """If django_debug_mode is True, this is automatically enabled"""
  return DJANGO_DEBUG_MODE.get()


INSTRUMENTATION = Config(
  key='instrumentation',
  help=_('Enable or disable instrumentation. If django_debug_mode is True, this is automatically enabled.'),
  type=coerce_bool,
  dynamic_default=get_instrumentation_default)


AUDIT_EVENT_LOG_DIR = Config(
  key="audit_event_log_dir",
  help=_("The directory where to store the auditing logs. Auditing is disable if the value is empty."),
  type=str,
  dynamic_default=get_navigator_audit_log_dir
)

AUDIT_LOG_MAX_FILE_SIZE = Config(
  key="audit_log_max_file_size",
  help=_("Size in KB/MB/GB for audit log to rollover."),
  type=str,
  dynamic_default=get_navigator_audit_max_file_size
)

DJANGO_SERVER_EMAIL = Config(
  key='django_server_email',
  help=_('Email address that internal error messages should send as.'),
  default='hue@localhost.localdomain'
)

DJANGO_EMAIL_BACKEND = Config(
  key="django_email_backend",
  help=_("The email backend to use."),
  type=str,
  default="django.core.mail.backends.smtp.EmailBackend"
)

CORS_ENABLED = Config(
  key="cors_enabled",
  help=_("Enable or disable Cross-Origin Resource Sharing (CORS). Defaults to True."),
  type=coerce_bool,
  default=True
)

CORS_ALLOW_CREDENTIALS = Config(
  key="cors_allow_credentials",
  help=_("This value determines whether the server allows cookies in the cross-site HTTP requests. Defaults to True."),
  type=coerce_bool,
  default=True
)

CORS_ALLOWED_ORIGINS = Config(
  key="cors_allowed_origins",
  help=_("A comma separated list of origins allowed for CORS."),
  type=coerce_csv
)

ENABLE_SQL_SYNTAX_CHECK = Config(
  key='enable_sql_syntax_check',
  default=True,
  type=coerce_bool,
  help=_('Choose whether to enable SQL syntax check or not.')
)

DISABLE_SOURCE_AUTOCOMPLETE = Config(
  key='disable_source_autocomplete',
  type=coerce_bool,
  default=False,
  help=_('Choose whether the editor autocomplete should gather suggestions from external sources or not.')
)

ENABLE_HUE_5 = Config(
  key="enable_hue_5",
  help=_("Feature flag to enable Hue 5."),
  type=coerce_bool,
  default=False
)

ENABLE_NEW_STORAGE_BROWSER = Config(
  key="enable_new_storage_browser",
  help=_("Feature flag to enable new Hue Storage browser."),
  type=coerce_bool,
  default=False
)

ENABLE_NEW_IMPORTER = Config(
  key="enable_new_importer",
  help=_("Feature flag to enable new Hue Importer."),
  type=coerce_bool,
  default=False
)


def is_chunked_fileuploader_enabled():
  return ENABLE_CHUNKED_FILE_UPLOADER.get()


ENABLE_CHUNKED_FILE_UPLOADER = Config(
  key="enable_chunked_file_uploader",
  help=_("Enable new chunked file uploader."),
  type=coerce_bool,
  default=False
)

USE_NEW_EDITOR = Config(  # To remove in Hue 4
  key='',
  default=True,
  type=coerce_bool,
  help=_('Choose whether to show the new SQL editor.')
)

ENABLE_DOWNLOAD = Config(
  key="enable_download",
  help=_(
    'Global setting to allow or disable end user downloads in all Hue (e.g. Query result in editors and dashboard, '
    'file in File Browser browsers...).'),
  type=coerce_bool,
  default=True)

ENABLE_SHARING = Config(
  key="enable_sharing",
  help=_(
    'Global setting to enable or disable document sharing. Note that this does not affect currently shared documents.'),
  type=coerce_bool,
  default=True)

ENABLE_WORKFLOW_CREATION_ACTION = Config(
  key="enable_workflow_creation_action",
  help=_('Global setting to enable or disable new workflow creation in Hue documents.'),
  type=coerce_bool,
  default=True,
)

USE_NEW_ASSIST_PANEL = Config(
  key='use_new_assist_panel',
  default=False,
  type=coerce_bool,
  help=_('Choose whether to show the new Assist panel or the old.')
)

USE_DEFAULT_CONFIGURATION = Config(
  key='use_default_configuration',
  default=False,
  type=coerce_bool,
  help=_('Enable saved default configurations for Hive, Impala, Spark, and Oozie.')
)

USE_NEW_CHARTS = Config(
  key='use_new_charts',
  default=False,
  type=coerce_bool,
  help=_('Choose whether to use new charting library across the whole Hue.')
)

# Deprecated
IS_MULTICLUSTER_ONLY = Config(
  key='is_multicluster_only',
  default=False,
  type=coerce_bool,
  help=_('Choose whether to pick configs only from [desktop] [[cluster]]')
)

# Deprecated
IS_K8S_ONLY = Config(
  key='is_k8s_only',
  default=False,
  type=coerce_bool,
  help=_('Choose whether to pick configs only from [desktop] [[cluster]]')
)

ENABLE_UNIFIED_ANALYTICS = Config(
  key='enable_unified_analytics',
  default=False,
  type=coerce_bool,
  help=_('Choose whether Hive represents Unified Analytics or not.')
)

ENABLE_ORGANIZATIONS = Config(
  key='enable_organizations',
  default=False,
  type=coerce_bool,
  help=_('Choose whether to allow multi tenancy or not.')
)


ENABLE_PROMETHEUS = Config(
  key='enable_prometheus',
  default=False,
  type=coerce_bool,
  help=_('Turn on Prometheus metrics end point /metrics.')
)


TRACING = ConfigSection(
  key="tracing",
  help=_("Tracing configuration."),
  members=dict(
    ENABLED=Config(
      key='enabled',
      default=False,
      type=coerce_bool,
      help=_('If tracing is enabled.')
    ),
    TRACE_ALL=Config(
      key='trace_all',
      default=False,
      type=coerce_bool,
      help=_('Trace all the requests instead of a few specific ones like the SQL Editor. Much noisiers.')
    ),
))


def task_server_default_result_directory():
  """Local directory to store task results."""
  return 'file://%s' % get_run_root('logs')


TASK_SERVER = ConfigSection(
  key="task_server",
  help=_("Task Server configuration."),
  members=dict(
    ENABLED=Config(
      key='enabled',
      default=False,
      type=coerce_bool,
      help=_('If resource intensive or blocking can be delegated to an already running task server.')
    ),
    BROKER_URL=Config(
      key='broker_url',
      default='amqp://guest:guest@localhost//',
      help=_('How the task server and tasks communicate.')
    ),
    CELERY_RESULT_BACKEND=Config(
      key='celery_result_backend',
      dynamic_default=task_server_default_result_directory,
      help=_('Where to store task results. Defaults to local file system path. Celery comes with a several other backends.')
    ),
    RESULT_CELERYD_OPTS=Config(
      key='celeryd_opts',
      default='--time-limit=300',
      help=_('Default options provided to the task server at startup.')
    ),
    BEAT_ENABLED=Config(
      key='beat_enabled',
      default=False,
      type=coerce_bool,
      help=_('Switch on the integration with the Task Scheduler.')
    ),
    BEAT_SCHEDULES_FILE=Config(
      key='beat_schedules_file',
      default='',
      type=str,
      help=_('Path to a file containing a list of beat schedules.')
    ),
    FETCH_RESULT_LIMIT=Config(
      key='fetch_result_limit',
      default=2000,
      type=coerce_positive_integer,
      help=_('Number of query results rows to fetch into the result storage.')
    ),
    RESULT_CACHE=Config(
      key='result_cache',
      type=str,
      help=_('Django file cache class to use to temporarily store query results'),
      default='{"BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://localhost:6379/0", '
      '"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},"KEY_PREFIX": "queries"}'
    ),
    RESULT_STORAGE=Config(
      key='result_storage',
      type=str,
      help=_('Django file storage class to use to persist query results'),
      default='{"backend": "django.core.files.storage.FileSystemStorage", "properties": {"location": "./logs"}}'
    ),
    EXECUTION_STORAGE=Config(
      key='execution_storage',
      type=str,
      help=_('Django cache to use to store temporarily used data during query execution. '
      'This is in addition to result_file_storage and result_backend.'),
      default='{"BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": "celery-hue"}'
    ),
))

TASK_SERVER_V2 = ConfigSection(
  key="task_server_v2",
  help=_("Task Server V2 configuration."),
  members=dict(
    ENABLED=Config(
      key='enabled',
      default=False,
      type=coerce_bool,
      help=_('If resource intensive or blocking can be delegated to an already running task server.')
    ),
    BROKER_URL=Config(
      key='broker_url',
      default='amqp://guest:guest@localhost//',
      help=_('How the task server and tasks communicate.')
    ),
    CELERY_RESULT_BACKEND=Config(
      key='celery_result_backend',
      dynamic_default=task_server_default_result_directory,
      help=_('Where to store task results. Defaults to local file system path. Celery comes with a several other backends.')
    ),
    RESULT_CELERYD_OPTS=Config(
      key='celeryd_opts',
      default='--time-limit=300',
      help=_('Default options provided to the task server at startup.')
    ),
    BEAT_ENABLED=Config(
      key='beat_enabled',
      default=False,
      type=coerce_bool,
      help=_('Switch on the integration with the Task Scheduler.')
    ),
    BEAT_SCHEDULES_FILE=Config(
      key='beat_schedules_file',
      default='',
      type=str,
      help=_('Path to a file containing a list of beat schedules.')
    ),
    FETCH_RESULT_LIMIT=Config(
      key='fetch_result_limit',
      default=2000,
      type=coerce_positive_integer,
      help=_('Number of query results rows to fetch into the result storage.')
    ),
    RESULT_CACHE=Config(
      key='result_cache',
      type=str,
      help=_('Django file cache class to use to temporarily store query results'),
      default='{"BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://localhost:6379/0", '
      '"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},"KEY_PREFIX": "queries"}'
    ),
    RESULT_STORAGE=Config(
      key='result_storage',
      type=str,
      help=_('Django file storage class to use to persist query results'),
      default='{"backend": "django.core.files.storage.FileSystemStorage", "properties": {"location": "./logs"}}'
    ),
    EXECUTION_STORAGE=Config(
      key='execution_storage',
      type=str,
      help=_('Django cache to use to store temporarily used data during query execution. '
      'This is in addition to result_file_storage and result_backend.'),
      default='{"BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": "celery-hue"}'
    ),
    CELERY_LOG_LEVEL=Config(
      key='celery_log_level',
      type=str,
      help=_('Log level of celery workers.'),
      default='INFO'
    ),
    CHECK_DISK_USAGE_AND_CLEAN_TASK_ENABLED=Config(
      key='check_disk_usage_and_clean_task_enabled',
      type=coerce_bool,
      help=_('enable this peroidic cleaner which checks disk usage and makes space for file uploads'),
      default=False
    ),
    CHECK_DISK_USAGE_AND_CLEAN_TASK_PERIODIC_INTERVAL=Config(
      key='check_disk_usage_and_clean_task_periodic_interval',
      type=coerce_positive_integer,
      help=_('set the time interval in seconds to run this peroidic cleaner which checks disk usage and makes space for file uploads'),
      default=1000
    ),
    DISK_USAGE_CLEANUP_THRESHOLD=Config(
      key='disk_usage_cleanup_threshold',
      type=coerce_positive_integer,
      help=_('Clean up files in /tmp folder if the disk usage is beyond the threshold'),
      default=90
    ),
    DISK_USAGE_AND_CLEAN_TASK_TIME_DELTA=Config(
      key='disk_usage_and_clean_task_time_delta',
      type=coerce_positive_integer,
      help=_('Clean up files older than timedelta. Unit of timedelta is minutes'),
      default=60
    ),
    CLEANUP_STALE_UPLOADS_IN_REDIS_ENABLED=Config(
      key='cleanup_stale_uploads_in_redis_enabled',
      type=coerce_bool,
      help=_('enable this peroidic cleaner which cleans up failed upload tasks stored in redis'),
      default=False
    ),
    CLEANUP_STALE_UPLOADS_IN_REDIS_PERIODIC_INTERVAL=Config(
      key='cleanup_stale_uploads_task_periodic_interval',
      type=coerce_positive_integer,
      help=_('set the time interval in seconds to run this peroidic cleaner which cleans up failed upload tasks stored in redis'),
      default=900
    ),
    CLEANUP_STALE_UPLOADS_TASK_TIME_DELTA=Config(
      key='cleanup_stale_uploads_task_time_delta',
      type=coerce_positive_integer,
      help=_('Redis keys of format Upload__* older than timedelta will be cleaned up. Unit of timedelta is minutes'),
      default=60
    ),
))


def has_channels():
  return WEBSOCKETS.ENABLED.get()


WEBSOCKETS = ConfigSection(
  key="websockets",
  help=_("Django channels Websockets configuration. Requires Python 3."),
  members=dict(
    ENABLED=Config(
      key='enabled',
      default=False,
      type=coerce_bool,
      help=_('If websockets channels are to be used for communicating with clients.')
    ),
    LAYER_HOST=Config(
      key='layer_host',
      default='127.0.0.1',
      help=_('Layer backend host.')
    ),
    LAYER_PORT=Config(
      key='layer_port',
      type=int,
      default=6379,
      help=_('Layer backend port.')
    ),
  )
)


def get_clusters(user):
  clusters = []
  cluster_config = CLUSTERS.get()

  # Backward compatibility when not using clusters
  if not cluster_config:
    clusters.append(
      (CLUSTER_ID.get(), {
        'id': CLUSTER_ID.get(),
        'name': CLUSTER_ID.get(),
        'type': 'direct',
        'credentials': {},
        }
      )
    )

  for i in cluster_config:
    # Get additional remote multi clusters
    clusters.append(
      (i, {
          'id': i,
          'name': cluster_config[i].NAME.get() or i,
          'type': cluster_config[i].TYPE.get(),
          'credentials': cluster_config[i].CREDENTIALS.get(),
        }
      )
    )

  return OrderedDict(clusters)


def has_multi_clusters():
  '''If Hue is configured to talk to more than one completely independent clusters'''
  return len(CLUSTERS.get()) > 1


def has_connectors():
  '''When the connector feature is turned on'''
  return ENABLE_CONNECTORS.get()


def is_cdw_compute_enabled():
  '''When the computes feature is turned on'''
  clusters = CLUSTERS.get()
  return bool(clusters and [c for c in clusters.values() if c.TYPE.get() == 'cdw'])


CLUSTERS = UnspecifiedConfigSection(
  "clusters",
  help="One entry for each additional remote cluster Hue can interact with.",
  each=ConfigSection(
    help=_("Id of the cluster."),
    members=dict(
      NAME=Config(
          "name",
          help=_("Nice name of the cluster to show to the user. Same as id if not specified."),
          default=None,
          type=str,
      ),
      TYPE=Config(
          "type",
          help=_("Type of cluster, e.g. plain, CM, Snowball..."),
          default='direct',
          type=str,
      ),
      CREDENTIALS=Config(
          "credentials",
          help=_("How to connect to the of remote cluster management system."),
          default='{}',
          type=coerce_json_dict,
      )
    )
  )
)

ENABLE_GIST = Config(
  key='enable_gist',
  default=True,
  type=coerce_bool,
  help=_('Turn on the Gist snippet sharing.')
)


def default_gist_preview():
  """Gist preview only enabled automatically in private setups."""
  return not ENABLE_ORGANIZATIONS.get()


ENABLE_GIST_PREVIEW = Config(
  key='enable_gist_preview',
  dynamic_default=default_gist_preview,
  type=coerce_bool,
  help=_('Add public description so that the link can be unfurled in a preview by websites like Slack.')
)

ENABLE_LINK_SHARING = Config(
  key='enable_link_sharing',
  default=True,
  type=coerce_bool,
  help=_('Turn on the direct link sharing of saved document.')
)

USE_THRIFT_HTTP_JWT = Config(
  key="use_thrift_http_jwt",
  help=_("Use JWT as Bearer header for authentication when using Thrift over HTTP transport."),
  type=coerce_bool,
  default=False
)

DISABLE_LOCAL_STORAGE = Config(
  key='disable_local_storage',
  default=False,
  type=coerce_bool,
  help=_("Hue uses Localstorage to keep the users settings and database preferences,"
         "please make this value true in case local storage should not be used.")
)

ENABLE_HELP_MENU = Config(
  key='enable_help_menu',
  default=True,
  type=coerce_bool,
  help=_("Whether or not to show the Help menu in the Sidebar")
)

ENABLE_CONNECTORS = Config(
  key='enable_connectors',
  default=False,
  type=coerce_bool,
  help=_('Turn on the Connector configuration and usage.')
)

CUSTOM_DASHBOARD_URL = Config(
  key='custom_dashboard_url',
  default='',
  type=coerce_string,
  help=_('Custom URL to use for the dashboard application and editor result charting')
)

CONNECTORS_BLACKLIST = Config(
  key='connectors_blacklist',
  default='',
  type=coerce_csv,
  help=_('Comma separated list of connector types to hide.')
)

CONNECTORS_WHITELIST = Config(
  key='connectors_whitelist',
  default=[],
  type=coerce_csv,
  help=_('If not empty, comma separated list of connector types to keep.')
)

CONNECTORS = UnspecifiedConfigSection(
  key='connectors',
  help=_("""Configuration options for connectors instances to external services"""),
  each=ConfigSection(
    help=_("Id of the connector."),
    members=dict(
      NICE_NAME=Config(
          "nice_name",
          help=_("Nice name of the connector to show to the user. Same as id if not specified."),
          default=None,
          type=str,
      ),
      DIALECT=Config(
          "dialect",
          help=_("The language or type of the integrated service. e.g. MySql, Hive, HDFS..."),
          default=None,
          type=str,
      ),
      INTERFACE=Config(
          "interface",
          help=_("The class of connector to use to connect to the service (optional)."),
          default=None,
          type=str,
      ),
      SETTINGS=Config(
          "settings",
          help=_("Json string of a list of name/value settings to configure the connector. "
          "e.g. '{\"name\": \"url\", \"value\": \"mysql://hue:hue@host:3306/hue\"}]'"),
          default='{}',
          type=coerce_json_dict,
      ),
    )
  )
)


def _get_raz_url():
  """Check if we can guess if Raz is configured"""
  from hadoop.core_site import get_raz_api_url  # Avoid circular import
  return get_raz_api_url()


def has_raz_url():
  """Check if we can guess if Raz is configured"""
  return bool(_get_raz_url())


SDXAAS = ConfigSection(
  key='sdxaas',
  help=_("""Configuration for SDXaaS JWT support."""),
  members=dict(
    TOKEN_URL=Config(
      key='token_url',
      help=_('Comma separated host URLs to fetch token from.'),
      type=coerce_string,
      default='',
    )
  )
)


def is_sdxaas_jwt_enabled():
  """Check if SDXaaS token url is configured"""
  return bool(SDXAAS.TOKEN_URL.get())


def handle_raz_api_auth():
  """Return RAZ authentication type from JWT (if SDXaaS token URL is set) or KERBEROS"""
  return 'jwt' if is_sdxaas_jwt_enabled() else 'kerberos'


RAZ = ConfigSection(
  key='raz',
  help=_("""Configuration for RAZ service integration"""),
  members=dict(
    IS_ENABLED=Config(
      key='is_enabled',
      help=_('Turns on the integration as ready to use'),
      type=coerce_bool,
      dynamic_default=has_raz_url,
    ),
    API_URL=Config(
        key='api_url',
        help=_('Endpoint to contact'),
        type=coerce_string,
        dynamic_default=_get_raz_url,
    ),
    API_AUTHENTICATION=Config(
        key='api_authentication',
        help=_('How to authenticate against: KERBEROS or JWT'),
        type=coerce_str_lowercase,
        dynamic_default=handle_raz_api_auth,
    ),
    AUTOCREATE_USER_DIR=Config(
      key='autocreate_user_dir',
      help=_('Autocreate the user home directory in the remote storage home path.'),
      type=coerce_bool,
      default=True,
    ),
    IS_RAZ_GS_ENABLED=Config(
      help=_('Enable integration with Google Storage for RAZ'),
      key='is_raz_gs_enabled',
      default=False,
      type=coerce_bool
    )
  )
)


QUERY_DATABASE = ConfigSection(
  key='query_database',
  help=_("""Configuration options for specifying the Query History Database."""),
  members=dict(
    ENGINE=Config(
      key='engine',
      help=_('Database engine, such as postgresql_psycopg2, mysql, or sqlite3.'),
      type=coerce_database,
      default='django.db.backends.sqlite3',
    ),
    NAME=Config(
      key='name',
      help=_('Database name, or path to DB if using sqlite3.'),
      type=str,
      default='das',
    ),
    USER=Config(
      key='user',
      help=_('Database username.'),
      type=str,
      default='',
    ),
    SCHEMA=Config(
      key='schema',
      help=_('Database schema, to be used only when public schema is revoked in postgres.'),
      type=str,
      default='public',
    ),
    PASSWORD=Config(
      key='password',
      help=_('Database password.'),
      private=True,
      type=str,
      default="",
    ),
    HOST=Config(
      key='host',
      help=_('Database host.'),
      type=str,
      default='',
    ),
    PORT=Config(
      key='port',
      help=_('Database port.'),
      type=coerce_port,
      default='',
    ),
    OPTIONS=Config(
      key='options',
      help=_('Database options to send to the server when connecting.'),
      type=coerce_json_dict,
      dynamic_default=default_database_options
    ),
  )
)


def validate_ldap(user, config):
  res = []

  if config.SEARCH_BIND_AUTHENTICATION.get():
    if config.LDAP_URL.get() is not None:
      bind_dn = config.BIND_DN.get()
      bind_password = get_ldap_bind_password(config)

      if bool(bind_dn) != bool(bind_password):
        if bind_dn is None:
          res.append((LDAP.BIND_DN,
                    str(_("If you set bind_password, then you must set bind_dn."))))
        else:
          res.append((LDAP.BIND_PASSWORD,
                      str(_("If you set bind_dn, then you must set bind_password."))))
  else:
    if config.NT_DOMAIN.get() is not None or \
        config.LDAP_USERNAME_PATTERN.get() is not None:
      if config.LDAP_URL.get() is None:
        res.append((config.LDAP_URL,
                    str(_("LDAP is only partially configured. An LDAP URL must be provided."))))

    if config.LDAP_URL.get() is not None:
      if config.NT_DOMAIN.get() is None and \
          config.LDAP_USERNAME_PATTERN.get() is None:
        res.append((config.LDAP_URL,
                    str(_("LDAP is only partially configured. An NT Domain or username "
                    "search pattern must be provided."))))

    if config.LDAP_USERNAME_PATTERN.get() is not None and \
        '<username>' not in config.LDAP_USERNAME_PATTERN.get():
      res.append((config.LDAP_USERNAME_PATTERN,
                  str(_("The LDAP username pattern should contain the special"
                  "<username> replacement string for authentication."))))

  return res


def validate_database(user):
  res = []
  cursor = connection.cursor()

  if connection.vendor == 'mysql':
    try:
      innodb_table_count = cursor.execute('''
        SELECT *
        FROM information_schema.tables
        WHERE table_schema=DATABASE() AND engine = "innodb"''')

      total_table_count = cursor.execute('''
        SELECT *
        FROM information_schema.tables
        WHERE table_schema=DATABASE()''')

      # Promote InnoDB storage engine
      if innodb_table_count != total_table_count:
        res.append(('PREFERRED_STORAGE_ENGINE', str(_('''We recommend MySQL InnoDB engine over
                                                      MyISAM which does not support transactions.'''))))

      if innodb_table_count != 0 and innodb_table_count != total_table_count:
        res.append(('MYSQL_STORAGE_ENGINE', str(_('''All tables in the database must be of the same
                                                      storage engine type (preferably InnoDB).'''))))
    except Exception as ex:
      LOG.exception("Error in config validation of MYSQL_STORAGE_ENGINE: %s", ex)
  elif 'sqlite' in connection.vendor:
    res.append(('SQLITE_NOT_FOR_PRODUCTION_USE', str(_('SQLite is only recommended for development environments. '
        'It might cause the "Database is locked" error. Migrating to MySQL, Oracle or PostgreSQL is strongly recommended.'))))

  # Check if django_migrations table is up to date
  try:
    from desktop import appmanager

    cursor.execute('''SELECT * from django_migrations''')
    migration_history_entries = [(entry[1], entry[2]) for entry in cursor.fetchall()]

    apps = appmanager.get_apps(user)
    apps.append(appmanager.get_desktop_module('desktop'))
    missing_migration_entries = []
    for app in apps:
      if app.migrations_path:
        for migration_file_name in glob.iglob(app.migrations_path + '/*.py'):
          migration_name = os.path.splitext(os.path.basename(migration_file_name))[0]
          if migration_name != "__init__" and (app.name, migration_name) not in migration_history_entries:
            missing_migration_entries.append((app.name, migration_name))

    if missing_migration_entries:
      res.append(('django_migrations', str(_(
        '''django_migrations table seems to be corrupted or incomplete.
        %s entries are missing in the table: %s''') % (len(missing_migration_entries), missing_migration_entries)))
      )
  except Exception:
    LOG.exception("Error in config validation of django_migrations")

  return res


def config_validator(user):
  """
  config_validator() -> [ (config_variable, error_message) ]

  Called by core check_config() view.
  """
  from beeswax.models import QueryHistory, SavedQuery, Session
  from desktop.lib import i18n
  from desktop.models import Document, Document2  # Avoid cyclic loop
  from desktop.settings import DOCUMENT2_MAX_ENTRIES  # Avoid cyclic loop

  res = []

  doc_count = Document.objects.count()
  if doc_count > DOCUMENT2_MAX_ENTRIES:
    res.append(('DOCUMENT_CLEANUP_WARNING', str(_('Desktop Document has more than %d entries: %d, '
                'please run "hue desktop_document_cleanup --cm-managed" to remove old entries' % (DOCUMENT2_MAX_ENTRIES, doc_count)))))

  doc2_count = Document2.objects.count()
  if doc2_count > DOCUMENT2_MAX_ENTRIES:
    res.append(('DOCUMENT2_CLEANUP_WARNING', str(_('Desktop Document2 has more than %d entries: %d, '
                'please run "hue desktop_document_cleanup --cm-managed" to remove old entries' % (DOCUMENT2_MAX_ENTRIES, doc2_count)))))

  session_count = Session.objects.count()
  if session_count > DOCUMENT2_MAX_ENTRIES:
    res.append(('SESSION_CLEANUP_WARNING', str(_('Desktop Session has more than %d entries: %d, '
                'please run "hue desktop_document_cleanup --cm-managed" to remove old entries' % (DOCUMENT2_MAX_ENTRIES, session_count)))))

  qh_count = QueryHistory.objects.count()
  if qh_count > DOCUMENT2_MAX_ENTRIES:
    res.append(('QueryHistory_CLEANUP_WARNING', str(_('Query History has more than %d entries: %d, '
                'please run "hue desktop_document_cleanup --cm-managed" to remove old entries' % (DOCUMENT2_MAX_ENTRIES, qh_count)))))

  sq_count = SavedQuery.objects.count()
  if sq_count > DOCUMENT2_MAX_ENTRIES:
    res.append(('SavedQuery_CLEANUP_WARNING', str(_('Saved Query has more than %d entries: %d, '
                'please run "hue desktop_document_cleanup --cm-managed" to remove old entries' % (DOCUMENT2_MAX_ENTRIES, sq_count)))))

  # Check if oozie is enabled and present in INSTALLED_APPS
  from desktop.settings import INSTALLED_APPS  # Avoid circular import
  if 'oozie' in INSTALLED_APPS:
    from oozie.models import Job
    job_count = Job.objects.count()
    if job_count > DOCUMENT2_MAX_ENTRIES:
      res.append(('OOZIEJOB_CLEANUP_WARNING', str(_('Oozie Job has more than %d entries: %d, '
                  'please run "hue desktop_document_cleanup --cm-managed" to remove old entries' % (DOCUMENT2_MAX_ENTRIES, job_count)))))

  if not get_secret_key():
    res.append((SECRET_KEY, str(_("Secret key should be configured as a random string. All sessions will be lost on restart"))))

  # Validate SSL setup
  if SSL_CERTIFICATE.get():
    res.extend(validate_path(SSL_CERTIFICATE, is_dir=False))
    if not SSL_PRIVATE_KEY.get():
      res.append((SSL_PRIVATE_KEY, str(_("SSL private key file should be set to enable HTTPS."))))
    else:
      res.extend(validate_path(SSL_PRIVATE_KEY, is_dir=False))

  # Validate encoding
  if not i18n.validate_encoding(DEFAULT_SITE_ENCODING.get()):
    res.append((DEFAULT_SITE_ENCODING, str(_("Encoding not supported."))))

  # Validate kerberos
  if KERBEROS.HUE_KEYTAB.get() is not None:
    res.extend(validate_path(KERBEROS.HUE_KEYTAB, is_dir=False))
    # Keytab should not be world or group accessible
    kt_stat = os.stat(KERBEROS.HUE_KEYTAB.get())
    if stat.S_IMODE(kt_stat.st_mode) & 0o077:
      res.append((KERBEROS.HUE_KEYTAB,
                  force_unicode(_("Keytab should have 0600 permissions (has %o).") %
                  stat.S_IMODE(kt_stat.st_mode))))

    res.extend(validate_path(KERBEROS.KINIT_PATH, is_dir=False))
    res.extend(validate_path(KERBEROS.CCACHE_PATH, is_dir=False))

  if LDAP.LDAP_SERVERS.get():
    for ldap_record_key in LDAP.LDAP_SERVERS.get():
      res.extend(validate_ldap(user, LDAP.LDAP_SERVERS.get()[ldap_record_key]))
  else:
    res.extend(validate_ldap(user, LDAP))

  # Validate MYSQL storage engine of all tables
  res.extend(validate_database(user))

  # Validate if oozie email server is active
  try:
    from oozie.views.editor2 import _is_oozie_mail_enabled

    if not _is_oozie_mail_enabled(user):
      res.append(('OOZIE_EMAIL_SERVER', str(_('Email notifications is disabled for Workflows and Jobs as SMTP server is localhost.'))))
  except Exception as e:
    LOG.warning('Config check failed because Oozie app not installed %s' % e)

  from notebook.api import _save_notebook
  from notebook.models import make_notebook

  notebook = make_notebook(name='test', editor_type='hive', statement='select "ทดสอบ"', status='ready')
  notebook_doc = None
  try:
    notebook_doc, save_as = _save_notebook(notebook.get_data(), user)
  except Exception:
    res.append(('DATABASE_CHARACTER_SET', str(
      _('Character set of <i>search</i> field in <i>desktop_document2</i> table is not UTF-8. <br>'
        '<b>NOTE:</b> Configure the database for character set AL32UTF8 and national character set UTF8.'))
      )
    )
  if notebook_doc:
    notebook_doc.delete()

  if 'use_new_editor' in USE_NEW_EDITOR.bind_to:
    res.append(('[desktop] use_new_editor', str(_('This configuration flag has been deprecated.'))))

  # Validate S3 configuration
  if USE_STORAGE_CONNECTORS.get():
    try:
      from desktop.lib.fs.s3.conf_utils import validate_s3_configuration

      # Validate storage connector configuration structure
      s3_errors = validate_s3_configuration()
      for error in s3_errors:
        res.append(("STORAGE_CONNECTOR_CONFIGURATION", error))

    except Exception as e:
      res.append(("STORAGE_CONNECTOR_CONFIGURATION", f"Failed to validate storage connector configuration: {e}"))

  return res


def get_redaction_policy():
  """
  Return the configured redaction policy.
  """

  return LOG_REDACTION_FILE.get()


def get_secret_key():
  secret_key = os.environ.get('HUE_SECRET_KEY')
  if secret_key is not None:
    return secret_key

  secret_key = SECRET_KEY.get()
  if not secret_key:
    secret_key = SECRET_KEY_SCRIPT.get()

  return secret_key


def get_ssl_password():
  password = os.environ.get('HUE_SSL_PASSWORD')
  if password is not None:
    return password

  password = SSL_PASSWORD.get()
  if not password:
    password = SSL_PASSWORD_SCRIPT.get()

  return password


def get_database_password():
  password = os.environ.get('HUE_DATABASE_PASSWORD')
  if password is not None:
    return password

  password = DATABASE.PASSWORD.get()
  if not password:
    password = DATABASE.PASSWORD_SCRIPT.get()

  return password


def get_smtp_password():
  password = os.environ.get('HUE_SMTP_PASSWORD')
  if password is not None:
    return password

  password = SMTP.PASSWORD.get()
  if not password:
    password = SMTP.PASSWORD_SCRIPT.get()

  return password


def get_ldap_bind_password(ldap_config):
  password = os.environ.get('HUE_LDAP_BIND_PASSWORD')
  if password is not None:
    return password

  password = ldap_config.BIND_PASSWORD.get()
  if not password:
    password = ldap_config.BIND_PASSWORD_SCRIPT.get()

  return password


PERMISSION_ACTION_GS = "gs_access"

GC_ACCOUNTS = UnspecifiedConfigSection(
  'gc_accounts',
  help=_('One entry for each GC account'),
  each=ConfigSection(
    help=_('Information about single GC account'),
    members=dict(
      JSON_CREDENTIALS=Config(
        key='json_credentials',
        type=str,
        default=None,
      ),
      DEFAULT_HOME_PATH=Config(
        key="default_home_path",
        type=str,
        default=None,
        help="Optionally set this for a different home directory path. e.g. gs://gethue"
      ),
    )
  )
)


def is_cm_managed():
  return 'cloudera-scm-agent' in os.path.realpath(os.getenv("HUE_CONF_DIR", get_desktop_root("conf")))


def is_gs_enabled():
  from desktop.lib.idbroker import conf as conf_idbroker  # Circular dependencies  desktop.conf -> idbroker.conf -> desktop.conf

  return ('default' in list(GC_ACCOUNTS.keys()) and GC_ACCOUNTS['default'].JSON_CREDENTIALS.get()) or is_raz_gs() or \
    conf_idbroker.is_idbroker_enabled('gs')


PERMISSION_ACTION_S3 = "s3_access"

USE_STORAGE_CONNECTORS = Config(
    key='use_storage_connectors',
    type=coerce_bool,
    default=True,
    help=_('Use storage connector system for multi-cloud object storage access')
)

STORAGE_CONNECTORS = UnspecifiedConfigSection(
  'storage_connectors',
  help=_('Storage connector definitions with bucket-specific configurations'),
  each=ConfigSection(
    help=_('Configuration for a single S3-compatible storage connector'),
    members=dict(
      PROVIDER=Config(
        key='provider',
        type=str,
        default='aws',  # aws, netapp, dell, generic
        help=_('Storage provider type (aws, netapp, dell, generic)')
      ),
      AUTH_TYPE=Config(
        key='auth_type',
        type=str,
        default='key',  # key, iam, raz, idbroker
        help=_('Authentication method (key, iam, raz, idbroker)')
      ),
      REGION=Config(
        key='region',
        type=str,
        default=None,
        help=_('Default AWS region (required for AWS provider)')
      ),
      ENDPOINT=Config(
        key='endpoint',
        type=str,
        default=None,
        help=_('Custom endpoint URL (required for non-AWS providers)')
      ),
      ACCESS_KEY_ID=Config(
        key='access_key_id',
        type=str,
        default=None,
        help=_('Access key ID (required for key auth)')
      ),
      SECRET_KEY=Config(
        key='secret_key',
        type=str,
        private=True,
        default=None,
        help=_('Secret access key (required for key auth)')
      ),
      IAM_ROLE=Config(
        key='iam_role',
        type=str,
        default=None,
        help=_('IAM role ARN to assume (for iam auth)')
      ),
      BUCKET_CONFIGS=Config(
        key='bucket_configs',
        type=coerce_json_dict,
        default='{}',
        help=_('Per-bucket configuration: {"bucket-name": {"default_home_path": "/path/", "region": "us-east-1"}}')
      ),
      OPTIONS=Config(
        key='options',
        type=coerce_json_dict,
        default='{}',
        help=_('Provider-specific configuration options as JSON')
      )
    )
  )
)


def has_gs_access(user):
  from desktop.auth.backend import is_admin
  return user.is_authenticated and user.is_active and (
    is_admin(user) or user.has_hue_permission(action="gs_access", app="filebrowser") or is_raz_gs())


def is_raz_gs():
  from desktop.conf import RAZ  # Must be imported dynamically in order to have proper value

  return (RAZ.IS_ENABLED.get() and RAZ.IS_RAZ_GS_ENABLED.get())


def get_ozone_conf_dir_default():
  """
  Get from environment variable OZONE_CONF_DIR or '/etc/ozone/conf'
  """
  return os.environ.get("OZONE_CONF_DIR", "/etc/ozone/conf")


PERMISSION_ACTION_OFS = "ofs_access"

OZONE = UnspecifiedConfigSection(
  "ozone",
  help="One entry for each Ozone cluster",
  each=ConfigSection(
    help="Information about a single Ozone cluster",
    members=dict(
      FS_DEFAULTFS=Config(
          "fs_defaultfs",
          help="The equivalent of fs.defaultFS (aka fs.default.name)",
          type=str,
          default=None
      ),
      LOGICAL_NAME=Config(
          "logical_name",
          default="",
          type=str,
          help=_('NameNode logical name.')
      ),
      WEBHDFS_URL=Config(
          "webhdfs_url",
          help="The URL to WebHDFS/HttpFS service. Defaults to the WebHDFS URL on the NameNode.",
          type=str,
          default=None
      ),
      SECURITY_ENABLED=Config(
          "security_enabled",
          help="Whether Ozone requires client to perform Kerberos authentication",
          type=coerce_bool,
          default=False
      ),
      SSL_CERT_CA_VERIFY=Config(
          "ssl_cert_ca_verify",
          help="Choose whether Hue should validate certificates received from the server.",
          dynamic_default=default_ssl_validate,
          type=coerce_bool
      ),
      TEMP_DIR=Config(
          "temp_dir",
          help="Ozone directory for temporary files",
          default='/tmp',
          type=str
      ),
      OZONE_CONF_DIR=Config(
          key="ozone_conf_dir",
          dynamic_default=get_ozone_conf_dir_default,
          help="Directory of the Ozone configuration. Defaults to the env variable OZONE_CONF_DIR when set, or '/etc/ozone/conf'",
          type=str
      ),
    )
  )
)


def is_ofs_enabled():
  return ('default' in list(OZONE.keys()) and OZONE['default'].get_raw() and OZONE['default'].WEBHDFS_URL.get())


def has_ofs_access(user):
  from desktop.auth.backend import is_admin
  return user.is_authenticated and user.is_active and (is_admin(user) or user.has_hue_permission(action="ofs_access", app="filebrowser"))


IMPORTER = ConfigSection(
  key='importer',
  help=_("""Configuration options for the importer."""),
  members=dict(
    IS_ENABLED=Config(
      key='is_enabled',
      help=_('Enable or disable the new importer functionality'),
      type=coerce_bool,
      default=False,
    ),
    RESTRICT_LOCAL_FILE_EXTENSIONS=Config(
      key='restrict_local_file_extensions',
      default=None,
      type=coerce_csv,
      help=_(
        'Security setting to specify local file extensions that are not allowed to be uploaded through the importer. '
        'Provide a comma-separated list of extensions including the dot (e.g., ".exe, .zip, .rar, .tar, .gz").'
      ),
    ),
    MAX_LOCAL_FILE_SIZE_UPLOAD_LIMIT=Config(
      key="max_local_file_size_upload_limit",
      default=157286400,  # 150 MiB
      type=int,
      help=_('Maximum local file size (in bytes) that users can upload through the importer. The default is 157286400 bytes (150 MiB).'),
    ),
  ),
)
